﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Objects;
using System.Linq;
using System.Text;
using System.Threading;
using System.Transactions;
using IBS.Model;
using IBS.ServiceContracts;

namespace IBS.Bll.Payments
{
    public class PaymentProcessor : ServiceBase
    {
        private const int TimeInterval = 60 / 2 * 1000;
        private const int PendingStatusDelay = 1000;

        private ManualResetEvent checkEvent;
        private IAccountService _accountService;
        private readonly ITransferService _transferService;
        private Thread thread;
        private ICurrencyService _currencyService;

        public PaymentProcessor(IIbsContext context, IAccountService accountService, ICurrencyService currencyService)
            : base(context)
        {
            _currencyService = currencyService;
            _accountService = accountService;
            checkEvent = new ManualResetEvent(false);
            thread = new Thread(ProcessCheck);
            thread.IsBackground = true;
            thread.Start();
            checkEvent.Set();
        }


        public IList<Transfer> GetActualTransfers()
        {
            IList<Transfer> result = null;

            var transfers = new List<Transfer>();
            result = _context.Transfers.Where(t =>
                (TransferStatus)t.Status == TransferStatus.Pending).Take(100).ToList();

            return result;
        }

        private void ProcessCheck()
        {
            while (checkEvent.WaitOne(TimeInterval))
            {
                ForcePayPayments();
            }
        }

        public void ForcePayPayments()
        {
            var transfers = GetActualTransfers();
            if (transfers != null && transfers.Count > 0)
            {
                foreach (var transfer in transfers)
                {
                    PayPayment(transfer);
                }
            }
        }


        #region Base Evaluation

        private void IncreaseAssets(string ISO, decimal amount)
        {
            var assetsAccount = _accountService.GetAssetsAccount(ISO);
            IncreaseBalance(assetsAccount, -amount);
        }

        private void DecreaseAssets(string ISO, decimal amount)
        {
            var assetsAccount = _accountService.GetAssetsAccount(ISO);
            IncreaseBalance(assetsAccount, +amount);
        }


        private void IncreaseBalance(Account account, decimal amount)
        {
            var balance = _accountService.GetCurrentBalance(account);
            balance.Amount += amount;
        }

        private void DecreaseBalance(Account account, decimal amount)
        {
            var balance = _accountService.GetCurrentBalance(account);
            balance.Amount -= amount;
        }

        private bool IsCurrencyOperation(Model.Transfer transfer)
        {
            return transfer.FromAccount.Currency.ISO != transfer.ToAccount.Currency.ISO;
        }

        private void SaveCurrencySelling(string ISO, decimal currencyAmount, decimal baseAmount)
        {
            var sellingPair = _accountService.GetSellingPair(ISO);

            //Запоминаем сколько валюты продали
            IncreaseBalance(sellingPair.CurrencyAccount, -currencyAmount);

            //Запоминаем сколько рублей на это потратили
            IncreaseBalance(sellingPair.BaseAccount, baseAmount);
        }

        private void SaveCurrencyBuying(string ISO, decimal currencyAmount, decimal baseAmount)
        {
            var buyingPair = _accountService.GetBuyinPair(ISO);

            //Запоминаем сколько валюты купили
            IncreaseBalance(buyingPair.CurrencyAccount, currencyAmount);

            //Запоминаем сколько рублей на это потратили
            IncreaseBalance(buyingPair.BaseAccount, -baseAmount);
        }

        #endregion

        private void ProcessTransfer(Transfer transfer)
        {
           // throw new Exception("Test exception");
            var fromAccount = transfer.FromAccount;
            var toAccount = transfer.ToAccount;

            if (fromAccount.Currency.ISO != transfer.operationISO && toAccount.Currency.ISO != transfer.operationISO)
            {
                throw new BusinessException("Not correct operation currency");
            }

            var currentBalance = _accountService.GetCurrentBalance(fromAccount);
            if ((TransferType) transfer.TransferType != TransferType.Deposit &&  currentBalance.Amount < transfer.FromAccountAmount)
                throw new BusinessException("You don't have necessary amount");

            DecreaseBalance(fromAccount, transfer.FromAccountAmount);
            IncreaseBalance(toAccount, transfer.ToAccountAmount);

            if (!IsCurrencyOperation(transfer)) return;

            if (fromAccount.Currency.ISO == _currencyService.GetBaseCurrencyISO())
            {
                SaveCurrencySelling(toAccount.Currency.ISO, transfer.ToAccountAmount, transfer.FromAccountAmount);
                return;
            }
            if (toAccount.Currency.ISO == _currencyService.GetBaseCurrencyISO())
            {
                SaveCurrencyBuying(fromAccount.Currency.ISO, transfer.FromAccountAmount, transfer.ToAccountAmount);
                return;
            }
            throw new BusinessException(String.Format("One of currency must be in {0} currency", _currencyService.GetBaseCurrencyISO()));
        }


        private void SuccessTransfer(Transfer transfer)
        {
            transfer.Status = (int)TransferStatus.Success;
            transfer.EndDate = DateTime.Now;

        }

        private void FailTransfer(Transfer transfer)
        {
            transfer.Status = (int)TransferStatus.Fail;

        }

        private void PayPayment(Transfer transfer)
        {


            //using (var scope = new TransactionScope(TransactionScopeOption.Required))
            //{
                try
                {
                    //Thread.Sleep(PendingStatusDelay);
                    ProcessTransfer(transfer);
                    SuccessTransfer(transfer);
                    // _context.SaveChanges();
                }
                catch (Exception exception)
                {
                    FailTransfer(transfer);
                }
                finally
                {
                    _accountService.UnReserveAmount(transfer.FromAccount.Number, transfer.FromAccountAmount);
                    try
                    {
                        _context.SaveChanges();
                    }
                    catch (OptimisticConcurrencyException concurrency)
                    {
                        //Запрос будет отработан в следующий раз
                        //очищаем контекст
                        _context.Refresh(RefreshMode.StoreWins, transfer.FromAccount);
                    }
                    
                }
            //}
        }

    }

}
